package com.cxx.purchasecharge.core.config;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Logger;

import com.cxx.purchasecharge.core.util.StringUtil;

/**
 * Configuration properties
 *
 * This class extends Java Properties by adding some utility methods for formatting
 * and working with the property values.  The CfgPropertyFactory class should be used
 * to obtain the CfgProperties instances from the proper implementation whether that
 * is a file system or the Enterprise Configuration System.
 *
 */
public class CxxProperties extends Properties
{
    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(CxxProperties.class);
    
    private String m_group = null;
    
    /**
     * Construct an empty CxxProperties class for the specified group.
     * @param group
     */
    public CxxProperties(String group)
    {
        super();
        m_group = group;
    }
    
    /**
     * Construct a CxxProperties class for the specified group and populate it
     * with the specified properties.
     * @param group
     * @param prop
     */
    public CxxProperties(String group, Properties prop)
    {
        super(prop);
        m_group = group;
    }

    /**
     * Add the name value pairs in the specified string array as properties.
     * @param namevalues
     */
    public void load(String[] namevalues)
    {
        if ( namevalues == null)
        {
            throw new IllegalArgumentException("namevalues cannot be null");
        }

        if ((namevalues.length % 2) != 0)
        {
            throw new IllegalArgumentException("name/value mismatch");
        }

        for(int i=0; i<namevalues.length; i=i+2)
        {
            put(namevalues[i], namevalues[i+1]);
        }

    }

    /**
     * Returns the group name these properties represent.
     * @return Group Name
     */
    public String getGroupName()
    {
        return m_group;
    }
    
    /**
     * To retrieve a property value of type int
     * @param name The property name
     * @return property value
     * @throws ConfigException if the property value is NOT a int type or doesn't exist.
     */
    public int getIntProperty(String name) throws ConfigException
    {

        if(StringUtil.isNullOrBlank(name))
        {
            throw new IllegalArgumentException("name cannot be null or empty string");
        }

        String value = (String)get(name);

        if ( value == null )
        {
            throw new ConfigException("The property of ("+name+") does not exist.");
        }

        try
        {
            return Integer.parseInt(value);
        }
        catch(NumberFormatException ne)
        {
            throw new ConfigException("The property of ("+name+") is not a int type ("+value+").");
        }
    }
    
    /**
     * To retrieve a property value of type int
     * @param name The property name
     * @param defaultValue
     * @return property value
     */
    public int getIntProperty(String name, int defaultValue)
    {

        if(StringUtil.isNullOrBlank(name))
        {
            throw new IllegalArgumentException("name cannot be null or empty string");
        }

        String value = (String)get(name);

        if ( value == null )
        {
            logger.debug("The property of ("+name+") does not exist, using default value of ("+defaultValue+").");
            return defaultValue;
        }

        try
        {
            return Integer.parseInt(value);
        }
        catch(NumberFormatException ne)
        {
            logger.debug("The property of ("+name+") is not a int type ("+value+"), using default value of ("+defaultValue+").");
            return defaultValue;
        }
    }
    

    /**
     * To retrieve a property value of type long
     * @param name The name of the property
     * @return property value
     * @throws ConfigException if the property value is NOT a long type or doesn't exist.
     */
    public long getLongProperty(String name) throws ConfigException
    {
        if(StringUtil.isNullOrBlank(name))
        {
            throw new IllegalArgumentException("name cannot be null or empty string");
        }

        String value = (String)get(name);

        if ( value == null )
        {
            throw new ConfigException("The property of ("+name+") does not exist.");
        }

        try
        {
            return Long.parseLong(value);
        }
        catch(NumberFormatException ne)
        {
            throw new ConfigException("The property of ("+name+") is not a long type ("+value+").");
        }
    }

    /**
     * To retrieve a property value of type long
     * @param name The name of the property
     * @param defaultValue
     * @return property value
     */
    public long getLongProperty(String name, long defaultValue)
    {
        if(StringUtil.isNullOrBlank(name))
        {
            throw new IllegalArgumentException("name cannot be null or empty string");
        }

        String value = (String)get(name);

        if ( value == null )
        {
            logger.debug("The property of ("+name+") does not exist, using default value of ("+defaultValue+").");
            return defaultValue;
        }

        try
        {
            return Long.parseLong(value);
        }
        catch(NumberFormatException ne)
        {
            logger.debug("The property of ("+name+") is not a long type ("+value+"), using default value of ("+defaultValue+").");
            return defaultValue;
        }
    }

    
    /**
     * To retrieve a property value of boolean type
     * @param name  The property name
     * @return property value
     * @throws ConfigException if the property value is NOT a boolean type or doesn't exist.
     */
    public boolean getBooleanProperty(String name) throws ConfigException
    {
        if(StringUtil.isNullOrBlank(name))
        {
            throw new IllegalArgumentException("name cannot be null or empty string");
        }

        String value = (String)get(name);

        if ( value == null )
        {
            throw new ConfigException("The property of ("+name+") does not exist.");
        }

        try
        {
            return Boolean.parseBoolean(value);
        }
        catch(NumberFormatException ne)
        {
            throw new ConfigException("The property of ("+name+") is not a boolean type ("+value+").");
        }
    }

       /**
     * To retrieve a property value of boolean type
     * @param name  The property name
     * @param defaultValue
     * @return property value
     */
    public boolean getBooleanProperty(String name, boolean defaultValue) throws ConfigException
    {
        if(StringUtil.isNullOrBlank(name))
        {
            throw new IllegalArgumentException("name cannot be null or empty string");
        }

        String value = (String)get(name);

        if ( value == null )
        {
            logger.debug("The property of ("+name+") does not exist, using default value of ("+defaultValue+").");
            return defaultValue;
        }

        try
        {
            return Boolean.parseBoolean(value);
        }
        catch(NumberFormatException ne)
        {
            logger.debug("The property of ("+name+") is not a boolean type ("+value+"), using default value of ("+defaultValue+").");
            return defaultValue;
        }
    }
    

    /**
     * Get a subProperty map. Finds all properties beginning with a prefix.
     * Stores these in a new Properties object with keys that have the prefix
     * removed. For example, 'User.name' will be stored using the key 'name' when
     * the input prefix argument is 'User.'.
     *
     * @param prefix The property name prefix to search for
     * @return Properties collection with child properties of the parent key
     *         prefix
     */
    public Properties getSubProperties (String prefix)
    {
        if (prefix == null)
        {
            throw new IllegalArgumentException ("prefix cannot be null");
        }

        int prefixLength = prefix.length();

        // create sub-Properties that will be returned
        Properties subProperties = new Properties();

        // get the key names in this property set
        for (Enumeration keys = propertyNames(); keys.hasMoreElements();)
        {
            String key = (String)keys.nextElement();
            if (key.startsWith(prefix))
            {
                String value = getProperty (key);
                subProperties.setProperty(key.substring(prefixLength), value);
            }
        }

        return(subProperties);
    }

    /**
     * To retrieve a property value 
     * @param name The property name
     * @param defaultValue
     * @return property value
     */
    public String getProperty(String name, String defaultValue)
    {

        if(StringUtil.isNullOrBlank(name))
        {
            throw new IllegalArgumentException("name cannot be null or empty string");
        }

        String value = getProperty(name);

        if ( value == null )
        {
            logger.debug("The property of ("+name+") does not exist, using default value of ("+defaultValue+").");
            return defaultValue;
        }

        return value;
    }

    /**
     * Get the entries in the map, sorted by key.
     *
     * Returns a list of the entries in the map, sorted by key. (The sort is case
     * insensitive.) Note that unlike the entrySet() method, these entries are
     * not backed by the map, so that changes to the map are not reflected in the
     * list. The list represents a snapshot of the contents of the map.
     *
     * @return A list of the entries in the map, sorted by key. Each item in the
     *         list is of type Map.
     */
    public List getSortedEntries()
    {
        ArrayList list = new ArrayList (super.entrySet());
        Collections.sort (list, new Comparator()
        {
            public int compare (Object o1, Object o2)
            {
                String name1 = ((String)((Map.Entry)o1).getKey()).toLowerCase();
                String name2 = ((String)((Map.Entry)o2).getKey()).toLowerCase();
                return name1.compareTo (name2);
            }
        }
        );
        return list;
    }


    /**
     * Format a property value.
     *
     * Read a property value and use it as the input pattern to MessageFormat.
     * Apply the pattern to the supplied arguments.
     *
     * This is a convenience function that combines the Properties.getProperty
     * method with the MessageFormat.format method.
     *
     * @param propertyName The property whose value is to be used as the
     *            MessageFormat pattern.
     * @param arguments The arguments (usually strings) to apply to the pattern.
     * @return The formatted pattern
     */
    public String getFormattedProperty (String propertyName, Object[] arguments)
    {
        String prop = getProperty (propertyName);
        if (prop != null  &&  arguments != null  &&  arguments.length > 0)
        {
            MessageFormat format = new MessageFormat (prop);
            prop = format.format (arguments);
        }
        return prop;
    }

    /**
     * Format a property value.
     *
     * Read a property value and use it as the input pattern to MessageFormat.
     * Apply the pattern to the supplied argument. This is a convenience function
     * to avoid the caller having to create an array of arguments. It is exactly
     * equivalent to getFormattedProperty (propertyName, new Object[] {arg0,
     * arg1, arg2})
     *
     * @param propertyName The property whose value is to be used as the
     *            MessageFormat pattern.
     * @param arg0 The first argument (usually a string) to apply to the pattern.
     * @param arg1 The second argument (usually a string) to apply to the
     *            pattern.
     * @param arg2 The third argument (usually a string) to apply to the pattern.
     * @return The formatted pattern
     */
    public String getFormattedProperty (String propertyName, Object arg0,
            Object arg1, Object arg2)
    {
        return getFormattedProperty (propertyName,
                new Object[] {arg0, arg1, arg2});
    }

    /**
     * Format a property value.
     *
     * Read a property value and use it as the input pattern to MessageFormat.
     * Apply the pattern to the supplied argument. This is a convenience function
     * to avoid the caller having to create an array of arguments. It is exactly
     * equivalent to getFormattedProperty (propertyName, new Object[] {arg0,
     * arg1})
     *
     * @param propertyName The property whose value is to be used as the
     *            MessageFormat pattern.
     * @param arg0 The first argument (usually a string) to apply to the pattern.
     * @param arg1 The second argument (usually a string) to apply to the
     *            pattern.
     * @return The formatted pattern
     */
    public String getFormattedProperty (String propertyName, Object arg0,
            Object arg1)
    {
        return getFormattedProperty (propertyName,
                new Object[] {arg0, arg1});
    }


    /**
     * Format a property value.
     *
     * Read a property value and use it as the input pattern to MessageFormat.
     * Apply the pattern to the supplied argument. This is a convenience function
     * to avoid the caller having to create an array of arguments. It is exactly
     * equivalent to getFormattedProperty (propertyName, new Object[] {arg0})
     *
     * @param propertyName The property whose value is to be used as the
     *            MessageFormat pattern.
     * @param arg0 The argument (usually a string) to apply to the pattern.
     * @return The formatted pattern
     */
    public String getFormattedProperty (String propertyName, Object arg0)
    {
        return getFormattedProperty (propertyName,
                new Object[] {arg0});
    }
}
